#include <stdint.h>
#include <string.h>

#include "uch.h"

/* How is utf-8 formatted and used in programs?
   A presentation by Wistahe
   So, you want to understand utf-8? Maybe you've heard how prevalent an
   encoding it has become, with just about every webpage uses it. Maybe you've
   heard about how genius its design is and want to understand why for
   yourself. Maybe you want to know how to actually use it, like what types of
   special function do you need to work with utf-8 strings, or do you need any
   at all? Well, here are some answers! At last!

   Utf-8 is a unicode variable-width character encoding that is backwards
   compatible with ascii. This means that all ascii encoded text is
   automatically valid utf-8. Thus most c string functions and most string
   functions in other langauges that work with ascii will still work with
   utf-8. However, since not all characters are a single byte (all non ascii
   characters are multibyte), some precautions must still be taken. Notably
   strlen still works as before, but the returned length can no longer be
   interpreted as the number of characters, but the number of bytes in the
   string (this works because the null terminator '\0' of an ascii c string is
   still the null terminator of a utf-8 c string). Also char can no longer
   contain all characters since some are multiple bytes long and iterating
   through the characters one by one (like in a text editor, check out the
   gap buffer data structure for why this might be useful) requires determining
   the length of each individual character that is iterated over. The functions
   in this file are sufficient for curbing most of these issues.

   How do you display utf-8? printf! Most terminals are already capable of
   displaying utf-8 without any extra work, assuming the encoding of your
   operating system is set to use utf-8 as its encoding, which it probably is.

   Finally, how is utf-8 encoded? Well, each utf-8 character is 1 to 4 bytes
   and has one of the forms:

   1 byte (ascii)  0xxxxxxx
   2 bytes         110xxxxx 10xxxxxx
   3 bytes         1110xxxx 10xxxxxx 10xxxxxx
   4 bytes         11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

   where x can be 1 or 0. This encoding has some useful features such as
   every byte not at the start of a character and only every byte not at
   the start of a character begins with 10. This means utf-8 can be iterated
   backwards and only the first byte of a character is needed to know how many
   bytes the character is.

   What has been described above is true for the most part,

   BUT I LIED!! 😈

   Well, sort of
   What I have referred to as a character is really something called a code
   point and does not always correspond to a single glyph (a glyph is a single
   unit that is displayed, the image). A code point is the basic unit of
   meaning in a utf-8 encoding, but some code points are used for formatting
   such as adding a diacritical mark or combining emojis.

   n + ̀ = ǹ (diacritical)

   👁 + '‍' + 🗨 = 👁️‍🗨️ (zero width joiner)

   Unfortunately, I have not yet added methods for dealing with these types
   of combinations in this library. But that may not be necessary, since every
   code point is a basic basic unit of meaning it may still make sense to
   iterate over them rather than glyphs since you may wish to delete a code
   point. So dealing with combining code points may be more a task of the
   display or a glyph library and can easily be built ontop of this
   present library.
*/

size_t
ch_lenf(const char *str)
{
	/* first byte of a utf-8 character contains length of character */
	uint8_t c = *str;
	size_t len = 0;

	/* Total length of character is determined by 1 bits before a 0 bit,
	   with ascii as the exception since an ascii character always starts
	   with 0 and with 4 bytes as the maximum length.
	   For ascii the first bit is 0
	   For a 2 byte character the first bits are 110
	   For a 3 byte character the first bits are 1110
	   For a 4 byte character the first bits are 11110
	   What we do here is shift the byte by 1 each loop and check
	   if the first bit is 0 by shifting away all other bits
	   and increment len otherwise.
	*/
	for (; c >> 7; ++len, c <<= 1);

	/* ascii character, first bit was 0 */
	if (!len)
		return 1;

	return len;
}

size_t
ch_lenb(const char *str)
{
	size_t len = 1;

	/* go back by one byte */
	--str;

	/* ascii, last bit not set */
	if (!(*str & (1 << 7)))
		return 1;

	/* Not at start if second to last bit is not set.
	   This is because all bytes in a character except the first are
	   prefixed with the bits 10. Since any multibyte character has
	   more than 1 byte and the number of bytes correspond to the number
	   of bits in the first byte of the character, the second bit at the
	   start always is 1.
	*/
	for (; !(*str & (1 << 6)); ++len, --str);

	return len;
}

size_t
ch_len(Uch ch)
{
	/* treat ch as string and find length */
	return ch_lenf((char *) &ch);
}


Uch
uch(const char *str, size_t *len)
{
	Uch ch = 0;
	size_t tmp;

	/* if len is NULL, do not write to it */
	if (!len)
		len = &tmp;

	*len = ch_lenf(str);
	memcpy(&ch, str, *len);
	return ch;
}
